/*
 * Copyright (c) 2003 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */
#ifndef RLD
#include <stdlib.h>
#include <stdio.h>
#include <strings.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#if defined(__APPLE__) || defined(__GLIBC__) || defined(__MINGW32__)
#define HAVE_FTS
#include <fts.h>
#endif
#include <sys/errno.h>
#include <errno.h> /* cctools-port */
#include "stuff/bool.h"
#include "stuff/SymLoc.h"
#include "stuff/ofile.h"
#include "stuff/errors.h"
#include "stuff/allocate.h"
#include "stuff/dylib_roots.h"

struct check_block {
    char *install_name;
    enum bool check_result;
};

static void check_for_dylib(
    struct ofile *ofile,
    char *arch_name,
    void *cookie);

char *
get_symfile_for_dylib(
char *install_name,
char *release_name,
enum bool *found_project,
enum bool disablewarnings,
enum bool no_error_if_missing)
{
    const char *symroot;

	symroot = symLocForDylib(install_name, release_name, found_project,
				 disablewarnings, no_error_if_missing);
	if(symroot == NULL)
	    return(NULL);
	return(find_dylib_in_root(install_name, symroot));
}

char *
get_dstfile_for_dylib(
char *install_name,
char *release_name,
enum bool *found_project,
enum bool disablewarnings,
enum bool no_error_if_missing)
{
    const char *dstroot;
    char *image_file_name;
    struct check_block block;
    struct stat stat_buf;

	dstroot = dstLocForDylib(install_name, release_name, found_project,
				 disablewarnings, no_error_if_missing);
	if(dstroot == NULL)
	    return(NULL);

	if(*install_name == '/'){
	    image_file_name = makestr(dstroot, install_name, NULL);
	    block.install_name = install_name;
	    block.check_result = TRUE;
	    /*
	     * To avoid the error message generated by ofile_process() if the
	     * file does not exist just move on to trying to find it in the
	     * dstroot.
	     */
	    if(disablewarnings == TRUE){
		if(stat(image_file_name, &stat_buf) == -1){
		    free(image_file_name);
		    goto try_to_find_in_dstroot;
		}
	    }
	    ofile_process(image_file_name, NULL, 0, TRUE,
			  TRUE, TRUE, FALSE, check_for_dylib, &block);
	    if(block.check_result == TRUE)
		return(image_file_name);
	    free(image_file_name);
	}
try_to_find_in_dstroot:
	return(find_dylib_in_root(install_name, dstroot));
	return(NULL);
}

char *
find_dylib_in_root(
char *install_name,
const char *root)
{
#ifdef HAVE_FTS
    char *base_name, start[MAXPATHLEN + 1], *image_file_name;
    char const *paths[2];
    FTS *fts;
    FTSENT *ftsent;
    struct check_block block;

	block.install_name = install_name;
	block.check_result = FALSE;

#ifdef BIG_DEBUG
	printf("In find_dylib_in_root(install_name = %s, root = %s)\n",
	       install_name, root);
#endif
	if(realpath(root, start) == NULL){
#ifdef DEBUG
	    printf("realpath() failed for: %s (%s, errno = %d)\n", root,
		   strerror(errno), errno);
#endif
	    return(NULL);
	}
#ifdef BIG_DEBUG
	printf("realpath() = %s for root: %s\n", start, root);
#endif

	base_name = strrchr(install_name, '/');
	if(base_name == NULL || base_name[1] == '\0')
	    base_name = install_name;
	else
	    base_name = base_name + 1;

	paths[0] = start;
	paths[1] = NULL;
	fts = fts_open((char * const *)paths, FTS_PHYSICAL, NULL);
	if(fts == NULL){
#ifdef DEBUG
	    printf("fts_open() failed for: %s (%s, errno = %d)\n", start,
		   strerror(errno), errno);
#endif
	    return(NULL);
	}

	while((ftsent = fts_read(fts)) != NULL){
#ifdef BIG_DEBUG
	    printf("fts_path = %s fts_name = %s\n",
		   ftsent->fts_path, ftsent->fts_name);
#endif
	    if(S_ISREG(ftsent->fts_statp->st_mode) &&
	       !S_ISLNK(ftsent->fts_statp->st_mode) &&
	       strcmp(base_name, ftsent->fts_name) == 0){
#ifdef BIG_DEBUG
		printf("got a match: fts_path = %s fts_name = %s\n",
		       ftsent->fts_path, ftsent->fts_name);
#endif
		/*
		 * Now that we found a file with the same base_name in the root
		 * check to see that it is a dynamic library. Assume it is an
		 * if it is not then the routine check_for_dylib() will 
		 * reset the check_result in the block passed to 
		 * it back to FALSE.
		 */
		block.check_result = TRUE;
		ofile_process(ftsent->fts_path, NULL, 0, TRUE,
			      TRUE, TRUE, FALSE, check_for_dylib,&block);
		if(block.check_result == TRUE){
		    image_file_name = allocate(ftsent->fts_pathlen + 1);
		    strcpy(image_file_name, ftsent->fts_path);
#ifdef BIG_DEBUG
		    printf("returning %s\n", image_file_name);
#endif
		    if(fts_close(fts) == -1)
			system_error("fts_close() failed");
		    return(image_file_name);
		}
	    }
	}
	if(errno != 0){
#ifdef DEBUG
	    printf("fts_read() failed for (%s, errno = %d)\n",
		   strerror(errno), errno);
#endif
	    if(fts_close(fts) == -1)
		system_error("fts_close() failed");
	    return(NULL);
	}
	if(fts_close(fts) == -1){
	    system_error("fts_close() failed");
	    return(NULL);
	}

#endif /* !defined(__OPENSTEP___) */

	return(NULL);
}

static
void
check_for_dylib(
struct ofile *ofile,
char *arch_name,
void *cookie)
{
    uint32_t i;
    struct check_block *block;
    struct load_command *lc;
    uint32_t ncmds;

#ifdef BIG_DEBUG
	printf("In check_for_dylib() ofile->file_name = %s",
	       ofile->file_name);
	if(arch_name != NULL)
	    printf(" arch_name = %s\n", arch_name);
	else
	    printf("\n");
#endif /* BIG_DEBUG */

	block = (struct check_block *)cookie;
        if(ofile->mh != NULL){
	    ncmds = ofile->mh->ncmds;
	} else if (ofile->mh64 != NULL) {
	    ncmds = ofile->mh64->ncmds;
	} else {
	    block->check_result = FALSE;
	    return;
	}

	lc = ofile->load_commands;
	for(i = 0; i < ncmds; i++){
	    if(lc->cmd == LC_ID_DYLIB){
		return;
	    }
	    lc = (struct load_command *)((char *)lc + lc->cmdsize);
	}
	block->check_result = FALSE;
	return;
}
#endif /* !defined(RLD) */
